#
# Copyright (C) [2020] Futurewei Technologies, Inc.
#
# FORCE-RISCV is licensed under the Apache License, Version 2.0
#  (the "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re


from common.path_utils import PathUtils
from common.sys_utils import SysUtils
from common.msg_utils import Msg
from common.datetime_utils import DateTime

from classes.summary import Summary, SummaryItem, SummaryGroups, SummaryLevel


class RegressionSummaryItem(SummaryItem):
    def __init__(self, arg_summary):
        super().__init__(arg_summary)
        self.force_result = None
        self.iss_result = None
        self.iss_level = None
        self.rtl_result = None
        self.rtl_level = None
        self.trace_cmp_result = None
        self.trace_cmp_level = None

        # save the initial generation information for this task item

    def commit_generate(self):

        # Msg.dbg( "RegressionSummaryItem::commit_generate" )
        self.force_result = SysUtils.ifthen(
            SysUtils.success(self.force_retcode),
            "PASS",
            SysUtils.ifthen(self.signal_id is None, "FAIL", "INCOMPLETE"),
        )
        self.force_level = SummaryLevel.Any

        if SysUtils.failed(self.force_retcode):
            self.force_level = SummaryLevel.Fail
            Msg.user("self.force_retcode = %s" % (str(self.force_retcode)))

        Msg.user("self.force_level = %s" % (str(self.force_level)))
        return SysUtils.ifthen(SysUtils.success(self.force_retcode), 1, 0)

    def commit_simulate(self):

        try:
            # check the return code for error and check to see for instruction
            # overrun
            if (
                SysUtils.success(self.iss_retcode)
                and (int(self.instr_count) < self.max_instr)
                and (int(self.instr_count) >= self.min_instr)
            ):
                self.iss_result = "PASS"
                self.iss_level = SummaryLevel.Any
                return 1

            self.iss_result = SysUtils.ifthen(
                self.signal_id is None, "FAIL", "INCOMPLETE"
            )
            self.iss_level = SummaryLevel.Fail
            return 0
        finally:
            pass
            # Msg.user( "Iss Result: %s" % ( self.iss_result ))

    # save the initial RTL run information for this task item
    def commit_rtl(self):
        # Msg.dbg( "RegressionSummaryItem::commit_generate" )
        if SysUtils.success(self.rtl_retcode):
            self.rtl_result = "PASS"
            self.rtl_level = SummaryLevel.Any
            commit_count = 1
        else:
            self.rtl_result = "FAIL"
            self.rtl_level = SummaryLevel.Fail
            Msg.user("self.rtl_retcode = %s" % (str(self.rtl_retcode)))
            commit_count = 0

        Msg.user("self.rtl_level = %s" % (str(self.rtl_level)))
        return commit_count

    def commit_trace_cmp(self):

        # Msg.dbg( "RegressionSummaryItem::commit_generate" )
        self.trace_cmp_result = SysUtils.ifthen(
            SysUtils.success(self.trace_cmp_retcode),
            "PASS",
            SysUtils.ifthen(self.signal_id is None, "FAIL", "INCOMPLETE"),
        )
        self.trace_cmp_level = SummaryLevel.Any

        if SysUtils.failed(self.trace_cmp_retcode):
            self.trace_cmp_level = SummaryLevel.Fail
            Msg.user(
                "self.trace_cmp_retcode = %s" % (str(self.trace_cmp_retcode))
            )

        Msg.user("self.trace_cmp_level = %s" % (str(SummaryLevel.Fail)))
        return SysUtils.ifthen(SysUtils.success(self.trace_cmp_retcode), 1, 0)

    def iss_success(self):

        # Check to see for instruction overrun
        if (
            SysUtils.success(self.iss_retcode)
            and (int(self.instr_count) < self.max_instr)
            and (int(self.instr_count) >= self.min_instr)
        ):
            return True
        return False

    def get_gen_line(self):

        my_gline = "Generate[%s] - Return Code: %2d, Test Dir: %s/%05d" % (
            self.force_result,
            self.force_retcode,
            self.task_id,
            self.task_index,
        )

        my_msg = self.seed
        Msg.dbg("Seed: %s" % (my_msg))
        if my_msg is not None:
            my_gline += ", Seed: %s" % (str(my_msg))

        # show the seed if one exists
        my_msg = self.force_msg
        if my_msg is not None:
            my_gline += ", Message: %s" % (str(my_msg))
        my_gline += "\n"
        return my_gline

    def get_iss_line(self):

        my_sline = None
        my_sline = "ISS-Sim[%s] - Return Code: %2d, Test Dir: %s/%05d" % (
            self.iss_result,
            self.iss_retcode,
            self.task_id,
            self.task_index,
        )

        my_msg = self.iss_message
        if my_msg is not None:
            my_sline += ", Message: %s" % (str(my_msg))

        my_sline += "\n"
        return my_sline

    def get_rtl_line(self):
        my_sline = None
        my_sline = "RTL-Sim[%s] - Return Code: %2d, Test Dir: %s/%05d" % (
            self.rtl_result,
            self.rtl_retcode,
            self.task_id,
            self.task_index,
        )

        my_msg = self.seed
        Msg.dbg("Seed: %s" % (my_msg))
        # show the seed if one exists
        if my_msg is not None:
            my_sline += ", Seed: %s" % (str(my_msg))

        # show the message if one exists
        my_msg = self.rtl_message
        if my_msg is not None:
            my_sline += ", Message: %s" % (str(my_msg))

        my_sline += "\n"
        return my_sline

    def get_trace_cmp_line(self):
        Msg.user("RegressionSummaryItem::get_trace_cmp_line - 0", "GOT HERE")

        my_tline = None
        my_tline = "Compare[%s] - Return Code: %2d, Test Dir: %s/%05d" % (
            self.trace_cmp_result,
            self.trace_cmp_retcode,
            self.task_id,
            self.task_index,
        )

        Msg.user("RegressionSummaryItem::get_trace_cmp_line - 1", "GOT HERE")
        Msg.user(
            "Compare[%s] - Return Code: %2d, Test Dir: %s/%05d"
            % (
                self.trace_cmp_result,
                self.trace_cmp_retcode,
                self.task_id,
                self.task_index,
            )
        )

        # show the seed if one exists
        my_msg = self.trace_cmp_msg
        Msg.user("RegressionSummaryItem::get_trace_cmp_line - 2", "GOT HERE")
        if my_msg is not None:
            Msg.user(
                "RegressionSummaryItem::get_trace_cmp_line - 3", "GOT HERE"
            )
            my_tline += ", Message: %s" % (str(my_msg))
        # show the seed if one exists
        my_tline += "\n"

        Msg.user("RegressionSummaryItem::get_trace_cmp_line - 4", "GOT HERE")

        return my_tline


class RegressionSummary(Summary):
    def __init__(self, arg_summary_path, arg_keep):

        super().__init__(arg_summary_path, arg_keep)
        self.gen_total = 0
        self.gen_passed = 0
        self.iss_total = 0
        self.iss_passed = 0
        self.rtl_total = 0
        self.rtl_passed = 0
        self.trace_cmp_total = 0
        self.trace_cmp_passed = 0
        self.task_total = 0
        self.start_time = DateTime.Time()
        self.total_cycle_count = 0
        self.total_instruction_count = 0

    def create_summary_item(self):
        my_item = RegressionSummaryItem(self)
        return my_item

    def commit_item(self, arg_item):

        # Msg.user( "def commit( self, arg_item ):" );
        self.task_total += 1

        # replaced tasks dict with defaultdict(list) in parent
        self.tasks[arg_item.task_id].append(arg_item)
        self.groups.add_item(arg_item)

        my_results = arg_item.commit()
        # Msg.lout( arg_item, "user", "Final Committed SummayItem .... " )
        Msg.user("Commit Results: %s" % (str(my_results)), "CMT-RESULTS")

        self.gen_total += my_results[0]
        self.gen_passed += my_results[1]
        self.iss_total += my_results[2]
        self.iss_passed += my_results[3]
        self.rtl_total += my_results[4]
        self.rtl_passed += my_results[5]
        self.trace_cmp_total += my_results[6]
        self.trace_cmp_passed += my_results[7]

    # Create a summary file to contain the results from every attempted
    # generation and simulation and process each operation and produce
    # summarized totals for quick analysis
    def process_summary(self, arg_sum_level=SummaryLevel.Fail):

        # Msg.user( "process_summary()", "REG-SUMMARY" )
        my_utcdt = DateTime.UTCNow()
        my_file_name = "%sregression_summary.log" % (
            PathUtils().include_trailing_path_delimiter(self.summary_dir)
        )
        # Msg.user( "Master Log File: %s" % ( my_file_name ))
        my_ofile = None
        myLines = []
        # First try to open file
        with open(my_file_name, "w") as my_ofile:
            try:
                my_ofile.write("Date: %s\n" % (DateTime.DateAsStr(my_utcdt)))
                my_ofile.write("Time: %s\n" % (DateTime.TimeAsStr(my_utcdt)))
                self.process_errors(my_ofile)
                my_instr_count, my_cycle_count = self.process_summary_tasks(
                    my_ofile, arg_sum_level
                )
                self.process_summary_totals(
                    my_ofile, my_instr_count, my_cycle_count
                )

            except Exception as arg_ex:
                Msg.error_trace()
                Msg.err("Processing Summary, " + str(arg_ex))

            finally:
                my_ofile.close()
                # Msg.set_label( "user", my_usr_lbl )

    # iterate through the tasks dictionary and process the iterms associated
    # with each task
    def process_summary_tasks(self, arg_ofile, arg_sum_level):
        Msg.blank("info")
        my_instr_count = 0
        my_cycle_count = 0
        # get the outer task
        for my_key in self.tasks:
            try:
                arg_ofile.write("\nTask: %s\n" % (my_key))
                task_instr_count, task_cycle_count = self.process_summary_task(
                    arg_ofile, self.tasks[my_key], arg_sum_level
                )
                my_instr_count += task_instr_count
                my_cycle_count += task_cycle_count
            except BaseException:
                Msg.error_trace()
                Msg.err("Processing Task %s, Skipping Item ...." % (my_key))

        self.total_instruction_count = my_instr_count
        self.total_cycle_count = my_cycle_count
        return my_instr_count, my_cycle_count

    # iterate through and process each item associated with this task
    def process_summary_task(self, arg_ofile, arg_task, arg_sum_level):
        my_instr_count = 0
        my_cycle_count = 0

        # Msg.lout( arg_task, "user", "process_summary_task" )
        for my_item in arg_task:
            try:
                if my_item.has_generate():
                    # post the generate results
                    my_outline = my_item.get_gen_line()
                    if my_outline is not None:
                        arg_ofile.write(my_outline)
                        if arg_sum_level >= my_item.force_level:
                            Msg.info(my_outline)

                if my_item.has_simulate():
                    # post the simulate results
                    my_outline = my_item.get_iss_line()
                    if my_outline is not None:
                        arg_ofile.write(my_outline)
                        if arg_sum_level >= my_item.iss_level:
                            Msg.info(my_outline)
                    my_instr_count += my_item.instr_count

                if my_item.has_rtl():
                    # post the rtl run results
                    my_outline = my_item.get_rtl_line()
                    if my_outline is not None:
                        arg_ofile.write(my_outline)
                        if arg_sum_level >= my_item.rtl_level:
                            Msg.info(my_outline)
                    my_cycle_count += my_item.cycle_count

                try:
                    if my_item.has_trace_cmp():
                        # post the simulate results
                        my_outline = my_item.get_trace_cmp_line()
                        if my_outline is not None:
                            arg_ofile.write(my_outline)
                            if arg_sum_level >= my_item.trace_cmp_level:
                                Msg.info(my_outline)

                except BaseException:

                    Msg.user("Exception Raised", "GOT HERE")

            except BaseException:
                Msg.error_trace()
                Msg.err("Processing Task Index: %s" % (str(my_item.index)))

        return my_instr_count, my_cycle_count

    # output the tasks summary and echo to stdout
    def process_summary_totals(
        self, arg_ofile, arg_instr_count, arg_cycle_count
    ):
        Msg.blank("info")

        # generator totals
        my_gfails = self.gen_total - self.gen_passed
        my_lines = "\nGenerate    : %3d\n" % (self.gen_total)
        my_lines += "Generate Fails: %3d\n" % (my_gfails)
        my_lines += "Generate Success Rate : %5.2f%%\n" % (
            SysUtils.percent(self.gen_passed, self.gen_total)
        )
        my_lines += "\n"

        my_sfails = 0
        if self.iss_total > 0:
            # ISS sim totals
            my_sfails = self.iss_total - self.iss_passed
            my_lines += "ISS Sim   : %3d\n" % (self.iss_total)
            my_lines += "ISS Sim Fails: %3d\n" % (my_sfails)
            my_lines += "ISS Sim Success Rate : %5.2f%%\n" % (
                SysUtils.percent(self.iss_passed, self.iss_total)
            )
            my_lines += "\n"

            my_lines += "Total Instructions Emulated: %3d\n" % (
                arg_instr_count
            )
            my_lines += "\n"

        my_rfails = 0
        if self.rtl_total > 0:
            # RTL sim totals
            my_rfails = self.rtl_total - self.rtl_passed
            my_lines += "RTL Sim   : %3d\n" % (self.rtl_total)
            my_lines += "RTL Sim Fails: %3d\n" % (my_rfails)
            my_lines += "RTL Sim Success Rate : %5.2f%%\n" % (
                SysUtils.percent(self.rtl_passed, self.rtl_total)
            )
            my_lines += "\n"

            my_lines += "Total Cycle Count: %3d\n" % (arg_cycle_count)
            my_lines += "\n"

        my_cfails = 0
        if self.trace_cmp_total > 0:
            # simulation totals
            my_cfails = self.trace_cmp_total - self.trace_cmp_passed
            my_lines += "Compared     : %3d\n" % (self.trace_cmp_total)
            my_lines += "Compare Fails: %3d\n" % (my_cfails)
            my_lines += "Compare Success Rate : %5.2f%%\n" % (
                SysUtils.percent(self.trace_cmp_passed, self.trace_cmp_total)
            )
            my_lines += "\n"

        # task totals
        my_task_total = self.task_total
        my_total_fails = my_gfails + my_sfails + my_cfails + my_rfails
        my_lines += "Total Tasks     : %3d\n" % (my_task_total)
        my_lines += "Task Fails      : %3d\n" % (my_total_fails)
        my_lines += "Task Success Rate: %5.2f%%\n" % (
            SysUtils.percent(my_task_total - my_total_fails, (my_task_total))
        )
        my_lines += "\n"
        my_lines += "Total Run Time: %0.5f Seconds" % (
            DateTime.Time() - self.start_time
        )

        Msg.blank("info")
        Msg.info(my_lines)
        Msg.blank("info")

        arg_ofile.write(my_lines)
